package com.dingwen.treasure.gateway.filter;

import cn.hutool.core.util.StrUtil;
import com.dingwen.treasure.base.constant.GlobalConstant;
import com.dingwen.treasure.base.exception.AuthException;
import com.dingwen.treasure.gateway.config.PathPatternConfig;
import com.dingwen.treasure.jwt.config.JwtProperties;
import com.dingwen.treasure.jwt.constant.JwtConstant;
import com.dingwen.treasure.jwt.uitl.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * 请求身份验证过滤器 </br>
 * <p>待学习点： webflux</p>
 *
 * @author dingwen
 * @date 2022/03/27
 */
@Component
@Slf4j
public class RequestAuthFilter implements GlobalFilter, InitializingBean {

    /**
     * 路径规则配置
     */
    @Resource
    private PathPatternConfig pathPatternConfig;

    @Lazy
    @Resource
    private JwtProperties jwtProperties;

    @Lazy
    @Resource
    private JwtUtil jwtUtil;

    /**
     * url 白名单
     */
    private List<PathPattern> ignorePathPatternList;

    @Override
    public void afterPropertiesSet() {
        ignorePathPatternList = new ArrayList<>(pathPatternConfig.getIgnores().size() + 1);
        // 初始化PathPattern
        pathPatternConfig.getIgnores().parallelStream()
                .forEach(ignoreUri -> {
                    // url 字符串转 PathPattern
                    PathPattern pathPattern = new PathPatternParser().parse(ignoreUri);
                    ignorePathPatternList.add(pathPattern);
                });
    }

    /**
     * 是需要身份验证
     * 返回true则表示不需要进行认证
     *
     * @param uri uri
     * @return boolean
     */
    private boolean isNeedAuth(String uri) {
        // PathContainer
        PathContainer pathContainer = PathContainer.parsePath(uri);
        Optional<PathPattern> optional = ignorePathPatternList
                .stream()
                .filter(item -> item.matches(pathContainer)).findFirst();
        return optional.isPresent();
    }


    @SuppressWarnings("ALL")
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // headers
        HttpHeaders headers = request.getHeaders();

        ServerHttpRequest.Builder mutate = request.mutate();
        // 添加默认用户id
        mutate.header(GlobalConstant.TREASURE_HEAD_USER_ID, GlobalConstant.TREASURE_HEAD_USER_ID_VALUE);

        // 请求 path
        String path = request.getURI().getPath();
        // 判断是否需要进行认证
        if (!isNeedAuth(path)) {

            if (!headers.containsKey(jwtProperties.getHeader())) {
                String msg = "未携带token";
                log.error(msg);
                throwsUnauthorized(exchange, msg);
            }

            // 验证token
            String token = headers.get(jwtProperties.getHeader()).get(0);

            String msg = StrUtil.format("token:{}格式错误", token);

            if (!token.startsWith(jwtProperties.getTokenPrefix())) {
                log.error(msg);
                throwsUnauthorized(exchange, msg);
            }

            Claims claims = jwtUtil.verify(token);
            // token处理
            tokenHandler(claims, response);

            // userId
            String userId = String.valueOf(claims.get(JwtConstant.USER_ID));
            // 请求头类型
            String tokenType = String.valueOf(claims.get(JwtConstant.TOKEN_TYPE));

            // user token
            // web token
            if (!(path.startsWith(GlobalConstant.TREASURE_MANAGE_URL_PREFIX)
                    && tokenType.equals(JwtConstant.WEB_TOKEN))
                    || (path.startsWith(GlobalConstant.TREASURE_USER_URL_PREFIX)
                    && tokenType.equals(JwtConstant.APP_TOKEN))) {
                log.error(msg);
                throwsUnauthorized(exchange, msg);
            }

            mutate.header(GlobalConstant.TREASURE_HEAD_USER_ID, userId);
            mutate.header(GlobalConstant.TREASURE_HEAD_TOKEN_TYPE, tokenType);
        }


        ServerHttpRequest serverHttpRequest = mutate.header(GlobalConstant.TREASURE_HEAD_TOKEN,
                GlobalConstant.TREASURE_HEAD_TOKEN_VALUE).build();

        ServerWebExchange newExchange = exchange.mutate().request(serverHttpRequest).build();

        return chain.filter(newExchange);
    }

    /**
     * token处理
     *
     * @param claims   索赔
     * @param response 响应
     */
    private void tokenHandler(Claims claims, ServerHttpResponse response) {
        if (jwtUtil.needRefresh(claims)) {
            String newToken = jwtUtil.refreshToken(claims);
            ResponseCookie responseCookie = ResponseCookie
                    .fromClientResponse(GlobalConstant.TREASURE_HEAD_TOKEN, newToken)
                    // 24小时
                    .maxAge(24 * 60 * 60)
                    // 生效路径
                    .path("/")
                    .build();
            response.addCookie(responseCookie);
        }
    }

    private void throwsUnauthorized(ServerWebExchange exchange, String msg) {
        log.error("鉴权异常处理,请求路径:{},消息:{}", exchange.getRequest().getPath(), msg);
        throw new AuthException(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.name());

    }

}
